#ifndef OT_TIMER_NET_HPP_
#define OT_TIMER_NET_HPP_

#include <ot/spef/spef.hpp>
#include <ot/timer/pin.hpp>
#include <ot/traits.hpp>

namespace ot {

// Forward declaration
class RctEdge;
class RctNode;
class Rct;

// ------------------------------------------------------------------------------------------------

// Class: RctNode
class RctNode {

  friend class Rct;
  friend class Net;
  friend class Timer;
  
  public:
    // The default constructors.
    // An RC node can be determined by a name. In an RC tree, a node can be
    // either a pin in the design, or a Steiner point generated by some
    // third-parties producing Steiner trees.
    RctNode() = default;
    RctNode(const std::string&);

    float load   (Split, Tran) const;
    float cap    (Split, Tran) const;
    float slew   (Split, Tran, float) const;
    float delay  (Split, Tran) const;
    float impulse(Split, Tran) const;
    float ldelay (Split, Tran) const;

    RctNode& load   (Split, Tran, float);
    RctNode& delay  (Split, Tran, float);
    RctNode& impulse(Split, Tran, float);
    RctNode& ldelay (Split, Tran, float);

    inline RctNode& name(std::string n) { _name = n; return *this; }
    inline const auto name() const { return _name; }

    inline const Pin* pin() const { return _pin; }
    inline Pin* pin() { return _pin; } // mutable
    inline RctNode& pin(Pin& p) { _pin = &p; return *this; }
    inline RctNode& pin(Pin* p) { _pin = p; return *this; }

    // Append an RCEdge into list of fanin or fanout.
    inline RctNode& append_fanin(RctEdge& e) { _fanin.emplace_back(&e); return *this; } 
    inline RctNode& append_fanout(RctEdge& e) { _fanout.emplace_back(&e); return *this; }

  private:

    std::string _name;                           

    TimingData<float, MAX_TRAN, MAX_SPLIT> _ures   ; 
    TimingData<float, MAX_TRAN, MAX_SPLIT> _ncap   ;
    TimingData<float, MAX_TRAN, MAX_SPLIT> _load   ; 
    TimingData<float, MAX_TRAN, MAX_SPLIT> _beta   ;
    TimingData<float, MAX_TRAN, MAX_SPLIT> _delay  ;
    TimingData<float, MAX_TRAN, MAX_SPLIT> _ldelay ;
    TimingData<float, MAX_TRAN, MAX_SPLIT> _impulse;

    std::list<RctEdge*> _fanin;
    std::list<RctEdge*> _fanout;

    // An RC node should have a unique pin normally.
    // Probably it is a Steiner node and definitely not a pin.
    Pin* _pin {nullptr};

    void _scale_capacitance(float);
};

// ------------------------------------------------------------------------------------------------

// Class: RctEdge
class RctEdge {

  friend class Rct;
  friend class Net;
  friend class Timer;
  
  public:
    // Note that this constructor requires the RC nodes to be reference
    // type! Be aware of the lifetime of nodes.
    RctEdge(RctNode&, RctNode&, float);

    const auto& from() const { return _from; }
    const auto& to() const { return _to; }

    inline float res() const;
    inline void res(float);
  
  private:

    RctNode& _from;
    RctNode& _to;
    
    float _res {0.0f};

    void _scale_resistance(float);
};

// Function: res
inline float RctEdge::res() const {
  return _res;
}

// Procedure: res
inline void RctEdge::res(float v) {
  _res = v; 
}

// ------------------------------------------------------------------------------------------------

// Class: Rct
class Rct {

  friend class Net;
  friend class Timer;

  public:

    void update_rc_timing();
    void insert_segment(const std::string&, const std::string&, float);
    void insert_node(const std::string&, float = 0.0f);
    void insert_edge(const std::string&, const std::string&, float);
    
    float total_ncap() const;
    float slew(const std::string&, Split, Tran, float) const;
    float delay(const std::string&, Split, Tran) const;
    inline Rct& increase_cap(const std::string&, float);

    inline size_t num_nodes() const;
    inline size_t num_edges() const;
    
    const RctNode* node(const std::string&) const;
    inline RctNode* node(const std::string& name) { return _node(name); }

    inline std::string name() const { return _name; }
    inline Rct& name(const std::string& n) { _name = n; return *this; }

    inline const RctNode* root() const { return _root; }
    inline RctNode* root() { return _root; }
    inline Rct& root(RctNode* r) { _root = r; return *this;}
    inline Rct& emplace_node(const std::string& name) {
      _nodes.emplace(name, RctNode(name));
      return *this;
    }

    inline const auto& nodes() const { return _nodes; }
    inline auto& nodes() { return _nodes; }

    inline const auto& edges() const { return _edges; }
    inline auto& edges() { return _edges; }

    inline Rct& emplace_edge(RctNode& from, RctNode& to, float res) {
      _edges.emplace(_edges.end(), from, to, res);
      return *this;
    }
    inline Rct& emplace_edge(RctEdge& e) {
      _edges.emplace(_edges.end(), std::move(e));
      return *this;
    }

    inline Rct& assign_root(const std::string& root) {
      _root = _node(root); return *this;
    }

  private:
    // The name of the RC tree -> the name of its corresponding net.
    std::string _name;

    // The root node of the RC tree.
    RctNode* _root {nullptr};

    // The map is maintained for retrieving RC nodes with specified names.
    std::unordered_map<std::string, RctNode> _nodes;

    // The RC edges connecting pairs of RC nodes should also be initialized
    // given a set of RC nodes and corresponding connections.
    std::list<RctEdge> _edges;

    void _update_load(RctNode*, RctNode*);
    void _update_delay(RctNode*, RctNode*);
    void _update_ldelay(RctNode*, RctNode*);
    void _update_response(RctNode*, RctNode*);
    void _scale_capacitance(float);
    void _scale_resistance(float);

    // This is an internal accessor to retrieve the pointer of an RC node
    // with a specified node name.
    RctNode* _node(const std::string&);
};

// Function: num_nodes
inline size_t Rct::num_nodes() const {
  return _nodes.size();
}

// Function: num_edges
inline size_t Rct::num_edges() const {
  return _edges.size();
}

inline Rct& Rct::increase_cap(const std::string& name, float delta) {
  auto& node = _nodes[name];
  FOR_EACH_EL_RF(el, rf) {
    node._ncap[el][rf] += delta;
  }
  return *this;
}

// ------------------------------------------------------------------------------------------------

// Class: Net
class Net {

  friend class Timer;
  friend class Arc;
  friend class Pin;
  
  struct EmptyRct {
    // This struct wraps a two-dimensional array with a specific size
    // (MAX_SPLIT (rows), MAX_TRAN (columns))
    std::array<std::array<float, MAX_TRAN>, MAX_SPLIT> load;
  };

  public:
    // Net class contains a default constructor, but Pin class does not.
    // The operator[] is not available for map with Net as value type.
    Net() = default;
    Net(const std::string&);

    inline const std::string& name() const { return _name; }
    inline size_t num_pins() const { return _pins.size(); }

    inline const Rct* rct() const { return std::get_if<Rct>(&_rct); }
    inline Rct* rct() { return std::get_if<Rct>(&_rct); }

    // The RCTree instance variable is actually a variant.
    inline Net& rct(Rct& tree) {
      std::variant<EmptyRct, Rct> tv = tree;
      this->_rct = std::move(tv);
      return *this;
    }
    inline auto& emplace_rct() {
      return _rct.emplace<Rct>();
    }
    inline auto& emplace_empty_rct() {
      return _rct.emplace<EmptyRct>();
    }

    // -------
    inline const std::list<Pin*> pins() const { return _pins; }
    inline std::list<Pin*> pins() { return _pins; }

    // Non-const accessors/mutators may be dangerous.
    inline const Pin* root() const { return _root; }
    inline Pin* root() { return _root; } // mutable accessor.
    inline Net& root(Pin& rt) { _root = &rt; return *this; } // mutator

    // Insert a new pin. Usually this kind of operations are not visible
    // to the users as it should be done in parsers.
    // When emulating the parser behaviours, they will be useful.
    inline Net& append(Pin& pin) { _pins.emplace_back(&pin); return *this; }
    inline bool is_rc_timing_updated() const { return _rc_timing_updated; }

  private:

    std::string _name;

    Pin* _root {nullptr};

    std::list<Pin*> _pins;

    std::variant<EmptyRct, Rct> _rct;

    std::optional<spef::Net> _spef_net;

    bool _rc_timing_updated {false};

    float _load(Split, Tran) const;

    std::optional<float> _slew(Split, Tran, float, Pin&) const;
    std::optional<float> _delay(Split, Tran, Pin&) const;
    
    void _update_rc_timing();
    void _attach(spef::Net&&);
    void _make_rct();
    //void _make_rct(const spef::Net&);
    void _insert_pin(Pin&);
    void _remove_pin(Pin&);
    void _scale_capacitance(float);
    void _scale_resistance(float);
}; 

};  // end of namespace ot. -----------------------------------------------------------------------

#endif






